#ifndef __EPSILON_3D__IMAGE_H__
#define __EPSILON_3D__IMAGE_H__

#include "../debug.h"

#define eImageFileTypes    Epsilon::Image::Types
#define eImagePixelFormats Epsilon::Image::Formats

#define eImageFileType     eImageFileTypes::FileType
#define eImagePixelFormat  eImagePixelFormats::PixelFormat

typedef unsigned char byte;

// TODO: Make a separate imaging library
namespace Epsilon {
  namespace Image {
    namespace Types {
      enum FileType {
        BMP,  ///<  Bitmap
        DDS,  ///<  Direct Draw Surface
        EXR,  ///<  OpenEXR
        JPG,  ///<  JPEG
        PNG,  ///<  Portable Network Graphic
        TGA,  ///<  Targa
        RAW,  ///<  Loaded from memory
      };
    }

    namespace Formats {
      enum PixelFormat {
        RGB,          ///< Red Green Blue
        BGR,          ///< Blue Green Red
        RGBA,         ///< Red Green Blue + Alpha
      };
    }
  }
}

class eImage {
public:
   eImage (int               width,
           int               height,
           eImagePixelFormat format,
           int               bpp,
           byte*             data,
           bool              owns_data = true);  ///< Construct a RAW image.
                                                 ///   * File format undefined...
  virtual ~eImage (void);


   // Selectors
  inline int      Width    (void) const { return m_width;        }; ///< Returns the image's height
  inline int      Height   (void) const { return m_height;       }; ///< Returns the image's width
  inline int      Bpp      (void) const { return m_bpp;          }; ///< Returns the image's pixel depth (bits per-pixel)
  inline int      Channels (void) const { return m_channels;     }; ///< Returns the image's channel count
  inline int      Size     (void) const { return m_width  *
                                                 m_height *
                                                 m_channels;     }; ///< Returns the image's decoded size (in bytes)

  inline int      Mipmaps  (void) const { return m_mipmap_count; }; ///< Returns the number of mipmaps

  inline
    const eImagePixelFormat&
                  Format   (void) const { return m_format;       }; ///< Returns the pixel format
  inline
    const eImageFileType&
                  FileType (void) const { assert (m_data);
                                          return m_type;         }; ///< Returns the image type

  inline byte*    Data     (void) const { assert (m_data);
                                          return m_data;         }; ///< Returns the raw bitmap data

  inline byte*    Pixel    (const int x,
                            const int y) const {
    return &m_data [(y * m_width * m_channels) + (x * m_channels)]; ///< Returns the pixel at (x,y)
  }
  


  //mutable virtual bool Load (void) = 0;

  /// Converts the data in this image to the specified format
  ///
  ///   NOTE: If buf is non-NULL, the convertex image will not own the data
  ///
  ///  * If the return value is non-NULL, a new image had to be created
  ///    and the user is responsible for deleting both the original image
  ///    and the new one.
  virtual eImage* Convert (eImagePixelFormat format, int bpp, byte** buf = NULL);


  ///
  /// Generates a DOT3 Normal Map (in Tangent Space) for this image
  ///
  ///  * Assumes the image is a height map, and that the height is
  ///    stored in the red channel.
  ///
  virtual eImage* GenerateDOT3 (void);

  ///
  /// Generates a DUDV (sometimes called DSDT) Map for this image
  ///
  ///                                           d u
  ///  DUDV is really short for the derivative: ---
  ///                                           d t
  ///
  ///  A DUDV map is very similar to a normal map. However, it stores
  ///  the change in direction, rather than the actual normal at a
  ///  particular texel.
  ///
  ///  * Assumes the image is a height map, and that the height is
  ///    stored in the red channel.
  ///
  //virtual eImage* GenerateDUDV (void);

protected:
  eImage (eImageFileType type);  ///< Derived classes must use this constructor

  eImageFileType    m_type;
  eImagePixelFormat m_format;

  // Shared Properties
  int            m_width;        ///< The width of the image in pixels
  int            m_height;       ///< The height of the image in pixels
  int            m_bpp;          ///< The number of bits per pixel
  int            m_channels;     ///< The number of channels per pixel

  // DDS Properties
  int            m_mipmap_count; ///< The number of mipmaps stored

  // Pixel Data
  byte*          m_data;         ///< The image pixel data

  bool           m_owns_data;    ///< If true, on destruction, the image will
                                 ///  delete m_data.
};

class ePalettedImage : public eImage {
public:
   ePalettedImage (int               width,
                   int               height,
                   eImagePixelFormat format,
                   int               bpp,
                   byte*             data,
                   const byte*       palette,
                   bool              owns_data = true);  ///< Construct a RAW image.
                                                         ///   * File format undefined...
  ~ePalettedImage (void);


  // Mutators
  void SetPalette (const byte* palette); /// bpp * num_channels

  /// Converts the data in this image to the specified format
  ///
  ///   NOTE: If buf is non-NULL, the convertex image will not own the data
  ///
  ///  * Returns a new eImage, which the user is responsible for deleting.
  virtual eImage* Convert (eImagePixelFormat format, int bpp, byte** buf = NULL);

protected:
  byte* m_palette;      ///< The image palette
};

class eFile;

namespace Epsilon {
  namespace Image {
    class TGA : public eImage {
    public:
      TGA (eFile& file) : eImage (eImageFileTypes::TGA) {
        set_file (file);
      };

      bool Load (void);

    private:
      void set_file (eFile&);

      eFile* file_;
    };
  }
}

eImage* LoadImage (eFile& image_file);

#endif __EPSILON_3D__IMAGE_H__
